package function;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import solver.GradientDescentSolver;
import solver.Results;

import multiarray.MultiArray;
import ccpv.Person;

public class ArslanConflictFunction extends ConflictFunction {
	Set<String> needed_constants = new HashSet<String>(); 
	private static double lambda = 0;
	private static  double I = 0;
	private static  double c = 0;
	private static  double f = 0;
	private static  double minPower = 1;
	private static  double epsilon = 2.2204E-16;
	private static int L = 0;
	

	private boolean isPopulationSet = false;
	private boolean isConstantsSet = false;
	private boolean isBetaSet = false;

	// protected boolean isThetaSet = false;
	
	private double[] Pi; 
	private double[][] beta;
	List<List<int[]>> coalitions;
	
	public ArslanConflictFunction(MultiArray<Person> population, HashMap<String,Double> constants) {
		needed_constants.add("lambda");
		needed_constants.add("I");
		needed_constants.add("c");
		needed_constants.add("f");
		needed_constants.add("minPower");
		//needed_constants.add("precision");
		theta = new double[0][0];
		beta = new double[0][0];
		Pi = new double[0];
		
		setPopulation(population);
		setConstants(constants);
		
		coalitions = new ArrayList<List<int[]>>();
	}
	
	public void setTheta(double[][] theta) {
		this.theta = solver.Utility.copyMatrix(theta);
	}
	

	private void setAltruism(float[][] beta) {
		this.beta = new double[beta.length][beta.length];
		for (int i = 0; i<beta.length; i++) {
			for (int j = 0; j<beta.length; j++) {
				this.beta[i][j] = (double)beta[i][j];
			}
		}
		isBetaSet = true;
	}

	@Override
	public void setCoalitions(List<List<int[]>> coalitions, float[][] beta) {
		this.coalitions = solver.Utility.copyListListIntArray(coalitions);
		setAltruism(beta);
		System.out.println("Coalitions:" + this.coalitions.size());
		for (int i = 0; i<coalitions.size(); i++) {
			System.out.println("  Size #"+ i + ": " + this.coalitions.get(i).size());
		}
		L = coalitions.size();
		if (isPopulationSet) {
			setPi();
		}
	}
	

	private void setPi() {
		if (!isPopulationSet) {
			System.out.println("Warning: Tried to calculate PI_i, but Population is not set.");
			return;
		}
		Pi = new double[L];
		for (int i=0; i<L; i++) {
			Pi[i] = Pi(coalitions.get(i));
		}
	}
	
	private double Pi(List<int[]> in) {	
		Person temp_person;
		int[] tempChar = new int[3];
		double tempPower = 0;
		for (int j = 0; j<in.size(); j++) {
			tempChar = in.get(j);
			temp_person = population.getElementAtIndex(tempChar); 
			tempPower += temp_person.getPower()*temp_person.getQty();
		}
		return tempPower;
	}
		
	private double pibar(int i) {
		return (1-c)*Pi[i]+c*this.theta[i][i];
	}
	
	@Override
	public void setPopulation(MultiArray<Person> population) {
		this.population = population; 
		isPopulationSet = true;
	}

	
	private double sum_denominator(int i, int j) {
		return (epsilon + theta[i][j]+theta[j][i]);
	}

	private double sum_numerator(int i, int j) {
		return theta[i][j]*pibar(j)-theta[j][i]*pibar(i);
	}


	public double utility_bar_derivative(int i, int m, int n) {
		if ((i == m) && (m == n)) { // d Ubar_i / d 0_i,i 
			return iii_deriv(i);
		}
		if ((i==m)&&(m != n)) { // d Ubar_i / d 0_i,j
			return iij_deriv(i,n);
		}
		if ((i != m)&&(m==n)) { // d Ubar_i / d 0_j,j
			return ijj_deriv(i,m);
		}
		if ((i!=m)&&(m!=n)&&(i==n)) { // d Ubar_i / d 0_j,i
		    return iji_deriv(i,m);	
		}
		return 0; // // d Ubar_i / d 0_j,k

	}
	
	private double iii_deriv(int i) {
		// d Ubar_i / d 0_i,i
		double value = 0;
		for (int j = 0; j<L; j++) {
		    if (i == j) continue; // skip this iteration
//		    System.out.println("sumnum: " + theta[j][i]);
			value = value + (theta[j][i])/(sum_denominator(j,i));
		}
//		System.out.println("lambda: " + lambda + " I: " + I + " eps: " + epsilon + " L:" + L + "  c:" + c);
		value = value * lambda/(L-1);
		value = 1 - value;
		value = value * c * (1-I);
		return value;
	}
  
	private double iij_deriv(int i, int j) {
		// d Ubar_i / d 0_i,j		
		double value = 0.0;
		double denom = sum_denominator(i,j);
		value = epsilon*pibar(j)+theta[j][i]*(pibar(j)+pibar(i));
		value = value / (denom*denom);
		value = value*(1-I)*(lambda/(L-1));
	//	System.out.println("iij_deriv: "+ value);
		return value;
	}
	
	private double ijj_deriv(int i,int j) {
		// d Ubar_i / d 0_j,j
		double value = 0.0;
		value = c*theta[i][j]/sum_denominator(i,j);
		value = value*(1-I)*(lambda/(L-1));
	//	System.out.println("ijj_deriv: "+ value);
		return value;
	}
	
	private double iji_deriv(int i,int j) {
		// d Ubar_i / d 0_j,i
		double value = 0.0;
		double denom = sum_denominator(i,j);
		value = epsilon*pibar(i) + theta[i][j]*(pibar(i)+pibar(j));
		value = value / (denom*denom);
		value = value*-1*(1-I)*(lambda/(L-1));
	//	System.out.println("iji_deriv: "+ value);
		return value;
	}
	

	private double utility_derivative(int i, int m, int n) {
		if (!isBetaSet) return utility_bar_derivative(i,m,n);
		
		double sum = 0.0;
		sum = utility_bar_derivative(i, m, n); // d Ubar_i / d 0_m,n
		for (int j = 0; j<L; j++) {
			if (i == j) continue;
			sum += beta[i][j] * utility_bar_derivative(j, m, n); // d Ubar_j / d 0_m,n
		}
		return sum;
	}

	
	public boolean isReady() {
		return isConstantsSet && isPopulationSet && isThetaSet;
	}
	
	@Override
	public void setConstants(HashMap<String,Double> constants) {
		for (Entry<String,Double> et: constants.entrySet()) {
			if (needed_constants.contains(et.getKey())) {
			  this.constants.put(et.getKey(), et.getValue());
			} else {
				System.out.println("Warning: You tried to set a constant '" + et.getKey() + "', but this constant is not needed.");
			}
		}
		boolean allSet = true;
		for (String st: needed_constants) {
			if (!this.constants.containsKey(st)) {
				System.out.println("Warning: The constant '" + st + "' is needed, but this constant was not set!");
				allSet = false;
			}
		}
		
		if (allSet && setConstantsPrimitive()) {
			System.out.println("All needed constants were set.");
			isConstantsSet = true;
		} else {
			System.out.println("Warning: A problem was encountered in setting the constants.");
			isConstantsSet = false;
		}
		
	}
	
	private boolean setConstantsPrimitive() {
		boolean allSet = true;
		try {
		  lambda = constants.get("lambda");
		  I = constants.get("I");
		  c = constants.get("c");
		  f = constants.get("f");
		  minPower = constants.get("minPower");
		  //precision = constants.get("precision");
		} catch (Exception e){
			allSet = false;
		}
		return allSet;
	}
	
	private double utility(int i) {
		if (!isBetaSet) return utility_bar(i);
		
		double result = 0.0;
		result = utility_bar(i);
		for (int j = 0; j<L; j++) {
			if (i==j) continue;
			result +=  beta[i][j]*utility_bar(j);
		}
		return result;	
	}
	
	
	private double utility_bar(int i) {
		double sum = 0.0;
		  // Perform summation
		  for (int j = 0; j<L; j++) {
			  if (i == j) continue;
			  sum += sum_numerator(i,j)/sum_denominator(i,j); 
		  }
		  // Multiply summation constant
		  sum = sum *(lambda/(L-1));
		  // Add on Pibar inside big parentheses
		  sum += pibar(i);
		  // Multiply constant outside of big parentheses
		  sum = sum*(1-I);
		  // Add on constant outside the parentheses
		  sum += I*Pi[i];
		  return sum;	
	}

	

	
	
	

	private void updateAgents(Results res) {
		
		if (!res.isConflict) {
			incrementPower(); //TODO: Check with Dr. Chai to see if this is called if there is no conflict.
			return;
		}
		
		setTheta(res.getThetasD());
		double[] sum = new double[L];
		double temp = 0.0;
		// expenditure function
		for (int i = 0; i < L; i++) {
			sum[i] = pibar(i);
		}
		
		for (int i = 0; i< L; i++) {
			for (int j = i; j< L; j++) {
				if (i==j) continue;
			    if (res.isWinner(i, j)) {
			    	temp = winloseValue(j);
			    	sum[i] += temp;
			    	sum[j] -= temp;
			    } else {
			    	temp = winloseValue(i);
			    	sum[i] -= temp;
			    	sum[j] += temp;
			    }
			}		
		}
		printPowers();
	for (int i = 0; i<L; i++){
		distributePower(i,sum[i]);
 	}
		printPowers();
		incrementPower();
		printPowers();
	}
	
	private void printPowers() {
		System.out.println("######### Coalition Powers: #########################");
		for (int i = 0; i<L; i++){
			System.out.println("Coalition #" + i);
		for (int[] guy: coalitions.get(i)) {
			solver.Utility.printArray(guy);
			System.out.println(" Power : " + population.getElementAtIndex(guy).getPower());
		}
		}
		
	}
	
	private void distributePower(int coal, double power) {
		System.out.println("Distributing power amount:" + power);
		// calculate the total number of people
		int count = 0;
		for (int[] guy: coalitions.get(coal)) {
			count += population.getElementAtIndex(guy).getQty();
		}
		double fraction = 0;
		if (count > 0) {
			fraction = power / count; // power going back to each person
		}
		if (fraction < minPower) fraction = minPower;
		System.out.println("Fraction:" + fraction);
		// redistribute based upon the number of people
		//Person p;
		for (int[] guy: coalitions.get(coal)) {
			System.out.println("MY POWER BEFORE CONFLICT: " + population.getElementAtIndex(guy).getPower());
            population.getElementAtIndex(guy).setPower(fraction);
      System.out.println("MY POWER AFTER CONFLICT: " + population.getElementAtIndex(guy).getPower());
		}
	}
	
	private void incrementPower() {
		Person p;
		for (List<int[]> coal: coalitions) {
			for (int[] guy: coal) {
				p = population.getElementAtIndex(guy);
				p.setPower(p.getPower()*(1+f));
			}
		}
	}
	
	private double winloseValue(int i) {
		 //Lambda * Pibar(i)/(L-1)
		return lambda*pibar(i)/(L-1);
	}

	@Override
	public double[] getBounds() {
		return solver.Utility.copyArray(Pi);
	}

	@Override
	public int getDimension() {
		return L;
	}

	@Override
	public Results solve_and_update_agents() {
		double[][] out = GradientDescentSolver.solve(this);
		float[] utility = solver.Utility.convertArrayDoubleFloat(getUtility(out));
		float[] utility_bar = solver.Utility.convertArrayDoubleFloat(getUtility_bar(out));
		Results res = new Results(I, solver.Utility.convertMatrixDoubleFloat(out),utility,utility_bar);
		updateAgents(res);
		return res;
	}

	private double[] getUtility_bar(double[][] theta) {
		setTheta(theta);
		double[] out = new double[L];
		for (int i = 0; i<L; i++) {
			out[i] = utility_bar(i);
		}
		return out;
	}

	private double[] getUtility(double[][] theta) {
		setTheta(theta);
		double[] out = new double[L];
		for (int i = 0; i<L; i++) {
			out[i] = utility(i);
		}
		return out;
	}

	@Override
	public double[][] getDerivativeMatrix(double[][] theta) {
		setTheta(theta);
		double[][] result = new double[L][L];
		for (int i = 0; i<L; i++) {
	    	for (int j = 0; j<L; j++) {
			  result[i][j] = utility_derivative(i, i, j); 
	    	}
		}
		return result;
	}

	@Override
	public float[] getUtilities(List<List<int[]>> partition, float[][] beta) {
		this.setCoalitions(partition, beta);
		double[][] out = GradientDescentSolver.solve(this);
		return solver.Utility.convertArrayDoubleFloat(getUtility(out));
	}
	
	
	
	
	
}
